package pl.pw.gis.gc_sa.gui;

import java.util.List;

import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Display;

import pl.pw.gis.gc_sa.data.Graph;
import pl.pw.gis.gc_sa.data.Vertex;

/**
 * Klasa implementujaca interfejs PaintListener wykorzystywana do rysowania
 * grafu
 * 
 * @author Lapek
 * 
 */
public class Painter implements PaintListener {
    private static final int DOT_SIZE = 10;
    Canvas mCanvas;
    Graph mGraph;
    Color[] mColors;

    int mBorderSize = 5;

    boolean mNeedsModifying;
    double mXAddedValue = 0;
    double mXMultiValue = 0;
    double mYAddedValue = 0;
    double mYMultiValue = 0;

    /**
     * Konstruktor klasy.
     * 
     * @param canvas
     *            obiekt Canvas "na ktorym" przeprowadzane jest rysowanie
     */
    public Painter(Canvas canvas) {
        mCanvas = canvas;
        initColors();
    }

    /**
     * Metoda ustawiajaca biezaco rysowany graf
     * 
     * @param graph
     *            graf do rysowania
     */
    public void setGraph(Graph graph) {
        mGraph = graph;

        if (mGraph != null) getAddedAndMultiValues();

        mCanvas.redraw();
    }

    @Override
    /**
     * Metoda wywolywana przez aplikacje przy zdarzeniu nowego odrysowywania. Rysuje graf lub jedynie napis o braku ustawionego grafu do rysowania
     * @param e obiekt PaintEvent wykorzystywany w rysowaniu
     */
    public void paintControl(PaintEvent e) {
        GC gc = e.gc;
        Rectangle clientArea = mCanvas.getClientArea();
        int w = clientArea.width;
        int h = clientArea.height;

        gc.drawLine(0, 0, w, 0);
        gc.drawLine(0, 0, 0, h);
        gc.drawLine(w - 1, 0, w - 1, h - 1);
        gc.drawLine(0, h - 1, w - 1, h - 1);
        // colorTest(gc, w, h);

        if (mGraph == null) {
            gc.drawText("No graph", w / 2, h / 2);
        } else {
            drawGraph(gc, w, h);
        }
    }

    /**
     * Rysuje graf
     * 
     * @param gc
     *            obiekt GC wykorzystywany do rysowania
     * @param w
     *            szerokosc obszaru rysowania w pikselach
     * @param h
     *            wysokosc obszaru rysowania w pikselach
     */
    private void drawGraph(GC gc, int w, int h) {
        drawConnections(gc, w, h);
        drawVertices(gc, w, h);
    }

    /**
     * Metoda rysujaca krawedzie grafu
     * 
     * @param gc
     *            obiekt GC wykorzystywany do rysowania
     * @param w
     *            szerokosc obszaru rysowania w pikselach
     * @param h
     *            wysokosc obszaru rysowania w pikselach
     */
    private void drawConnections(GC gc, int w, int h) {
        List<Vertex> vertices = mGraph.getVertices();

        for (int i = 0; i < mGraph.getVerticesCount(); i++) {
            Vertex v = vertices.get(i);
            for (Vertex n : v.getAdjacent()) {
                if (n.getId() > v.getId()) {
                    gc.drawLine(getVertexX(v, w), getVertexY(v, h),
                            getVertexX(n, w), getVertexY(n, h));
                }
            }
        }
    }

    /**
     * Metoda rysujaca wierzcholki grafu. Niepokolorowany graf ma wszystkie
     * wierzcholki czarne, dla pokolorowanego wykorzystywane jest przygotowana
     * lista kolorow. Jako ze wykorzystywanie zbyt wielu kolorow skutkowalo ich
     * myleniem sie i mniejsza czytelnoscia grafow, wykorzystujemy tylko 27 -
     * liczba wystarczajaca do kolorowania malych grafow, a dla wiekszych grafow
     * wyniki kolorowania i tak sa praktycznie nieczytelne kolorow
     * 
     * @param gc
     *            obiekt GC wykorzystywany do rysowania
     * @param w
     *            szerokosc obszaru rysowania w pikselach
     * @param h
     *            wysokosc obszaru rysowania w pikselach
     */
    private void drawVertices(GC gc, int w, int h) {
        for (Vertex v : mGraph.getVertices()) {
            gc.setBackground(mColors[v.getColor() % mColors.length]);
            gc.fillOval(getVertexX(v, w) - DOT_SIZE / 2, getVertexY(v, h)
                    - DOT_SIZE / 2, DOT_SIZE, DOT_SIZE);
        }
    }

    /**
     * Metoda inicjujaca liste kolorow
     */
    private void initColors() {
        int startVal = 85;
        int addVal = 85;

        int size = (255 - startVal) / addVal + 1;
        size = size * size * size;

        int count = 0;
        mColors = new Color[size];
        for (int r = startVal; r < 256; r += addVal) {
            for (int g = startVal; g < 256; g += addVal) {
                for (int b = startVal; b < 256; b += addVal) {
                    mColors[count++] = new Color(Display.getCurrent(), r, g, b);
                }
            }
        }
    }

    /**
     * Metoda wykorzystywana do wyliczania wartosci sluzacych do rozciagania i
     * translacji grafu, w sytuacji jesli wartosci x lub y pozycji wierzcholkow
     * wykraczaja poza domyslny przedzial <0,1>
     */
    private void getAddedAndMultiValues() {
        mNeedsModifying = false;
        mXAddedValue = 0;
        mXMultiValue = 0;
        mYAddedValue = 0;
        mYMultiValue = 0;
        for (Vertex v : mGraph.getVertices()) {
            if (v.getX() < mXAddedValue)
                mXAddedValue = v.getX();
            else if (v.getX() > mXMultiValue) mXMultiValue = v.getX();

            if (v.getY() < mYAddedValue)
                mYAddedValue = v.getY();
            else if (v.getY() > mYMultiValue) mYMultiValue = v.getY();
        }

        if (mXAddedValue < 0 || mXMultiValue > 1 || mYAddedValue < 0
                || mYMultiValue > 1) {
            mNeedsModifying = true;

            mXAddedValue = -mXAddedValue;
            mYAddedValue = -mYAddedValue;
            mXMultiValue += mXAddedValue;
            mYMultiValue += mYAddedValue;

        }
    }

    /**
     * Metoda zwracaja wartosc wspolczynnika x pozycji wierzcholka v w pikselach 
     * @param v wierzcholek
     * @param w szerokosc obszaru rysowania
     * @return wartosc pozycji x wierzcholka w pikselach
     */
    private int getVertexX(Vertex v, int w) {
        boolean useBorders = false;
        if (w > 2 * mBorderSize) {
            w -= 2 * mBorderSize;
            useBorders = true;
        }
        int val;
        if (mNeedsModifying) {
            val = (int) (((v.getX() + mXAddedValue) / mXMultiValue) * w);
        } else {
            val = (int) (v.getX() * w);
        }

        return useBorders ? val + mBorderSize : val;
    }

    /**
     * Metoda zwracaja wartosc wspolczynnika y pozycji wierzcholka v w pikselach 
     * @param v wierzcholek
     * @param h wysokosc obszaru rysowania
     * @return wartosc pozycji y wierzcholka w pikselach
     */
    private int getVertexY(Vertex v, int h) {
        boolean useBorders = false;
        if (h > 2 * mBorderSize) {
            h -= 2 * mBorderSize;
            useBorders = true;
        }
        int val;
        if (mNeedsModifying) {
            val = (int) (((v.getY() + mYAddedValue) / mYMultiValue) * h);
        } else {
            val = (int) (v.getY() * h);
        }
        return useBorders ? val + mBorderSize : val;
    }
}
